/*
 * @Author: jack-ZM
 * @Date: 2022-06-20 22:14:40
 * @LastEditors: jack-ZM
 * @LastEditTime: 2023-02-22 16:02:35
 * @FilePath: /yqm-front/src/service/request/index.ts
 * @Description:封装axios类 包括请求 取消请求 拦截响应 等操作
 *
 * Copyright (c) 2022 by zsm smzhang@fiberhome.com, All Rights Reserved.
 */
import axios, { AxiosResponse, AxiosError } from 'axios';
import qs from 'qs';
import type { AxiosInstance } from 'axios';
import { ElMessage } from 'element-plus';
import type {
    RequestConfig,
    RequestInterceptors,
    CancelRequestSource,
    CancelConfig
} from './types';

class Request {
    // axios 实例
    instance: AxiosInstance;
    // 拦截器对象
    interceptorsObj?: RequestInterceptors<AxiosResponse>;

    /*
  存放取消方法的集合
  * 在创建请求后将取消请求方法 push 到该集合中
  * 封装一个方法，可以取消请求，传入 url: string|string[]  
  * 在请求之前判断同一URL是否存在，如果存在就取消请求
  */
    cancelRequestSourceList?: CancelRequestSource[];
    constructor(config: RequestConfig) {
        this.cancelRequestSourceList = [];
        this.instance = axios.create(config);
        this.interceptorsObj = config.interceptors;
        // 拦截器执行顺序 (单个请求) -> 实例请求  -> 实例响应 -> (单个请求响应)
        // 使用实例拦截器 请求拦截
        this.instance.interceptors.request.use(this.interceptorsObj?.requestInterceptors);
        //使用实例拦截器 响应拦截
        this.instance.interceptors.response.use(this.interceptorsObj?.responseInterceptors);
    }
    request<T>(config: RequestConfig<T>): Promise<T> {
        return new Promise((resolve, reject) => {
            // 如果我们为单个请求设置拦截器，这里使用单个请求的拦截器
            if (config.interceptors?.requestInterceptors) {
                config = config.interceptors.requestInterceptors(config);
            }
            if (config.arrayFormat) {
                config.data = qs.stringify(config.data, { arrayFormat: config.arrayFormat });
            }
            const url = config.url;
            const cancelKey = getCancelKey(config);
            if (url) {
                // url存在保存取消请求方法
                config.cancelToken = new axios.CancelToken((c) => {
                    this.cancelRequestSourceList?.push({
                        [cancelKey]: c
                    });
                });
            }
            this.instance
                .request<any, T>(config)
                .then((res) => {
                    // 如果我们为单个响应设置拦截器，这里使用单个响应的拦截器
                    if (config.interceptors?.responseInterceptors) {
                        res = config.interceptors.responseInterceptors(res);
                    }
                    resolve(res);
                })
                .catch((err: AxiosError) => {
                    //拦截抛出接口请求失败原因
                    ElMessage.error(err.message);
                    reject(err);
                })
                .finally(() => {
                    //删除对应的cancel方法
                    url && this.delKeyCancel(cancelKey);
                });
        });
    }
    /**
     * @description: 获取指定 url 在 cancelRequestSourceList 中的索引
     * @param {string} url
     * @returns {number} 索引位置
     */
    private getSourceIndex(url: string): number {
        return this.cancelRequestSourceList?.findIndex((item: CancelRequestSource) => {
            return Object.keys(item)[0] === url;
        }) as number;
    }
    /**
     * @description: 删除 对应 cancelRequestSourceList
     * @param {string} key
     * @returns {*}
     */
    private delKeyCancel(cancelKey: string) {
        const sourceIndex = this.getSourceIndex(cancelKey);
        // 删除cancel方法
        sourceIndex !== -1 && this.cancelRequestSourceList?.splice(sourceIndex as number, 1);
    }

    // 取消请求
    cancelRequest(cancelKey: string | string[]) {
        if (typeof cancelKey === 'string') {
            // 取消单个请求
            const sourceIndex = this.getSourceIndex(cancelKey);
            sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][cancelKey]();
        } else {
            // 存在多个需要取消请求的地址
            cancelKey.forEach((u) => {
                const sourceIndex = this.getSourceIndex(u);
                sourceIndex >= 0 && this.cancelRequestSourceList?.[sourceIndex][u]();
            });
        }
    }
    // 取消全部请求
    cancelAllRequest() {
        this.cancelRequestSourceList?.forEach((source) => {
            const key = Object.keys(source)[0];
            source[key]();
        });
        this.cancelRequestSourceList = [];
    }
}
export default Request;

// 获取请求url唯一的key
export function getCancelKey<T>(config: CancelConfig | RequestConfig<T>) {
    return `${config.method?.toUpperCase()}${config.url}${
        config.data ? JSON.stringify(config.data) : ''
    }`;
}
